<template>
  <div class="upload-image">
    <ul class="picture-list" :class="{'cut-mb10': fileList.length}" v-if="props.type === 'image'">
      <li class="picture card" v-for="(item, i) in fileList" :key="i" v-loading="!item.type">
        <to-image
          v-if="item.type === 'img'"
          :ref="(el: any) => { if (el) item.ref = el }"
          fit="contain"
          hide-on-click-modal
          :src="item.src"
        />
        <template v-else>
          <SvgIcon :font="item.type" :size="30" />
          <p :title="item.name">
            {{ item.name }}
          </p>
        </template>
        <div class="card-actions">
          <el-icon v-if="item.type === 'img'" @click="previewImg(item)">
            <ZoomIn />
          </el-icon>
          <el-icon v-else @click="download(item)">
            <Download />
          </el-icon>
          <el-icon @click="deleteImg(item, i)" v-if="!inputDisabled">
            <Delete />
          </el-icon>
        </div>
        <div class="up-progress" v-show="item.progress && item.progress < 100">
          <p>{{ item.progress || 0 }}%</p>
          <div class="progress-bar" :style="{width: `${item.progress}%`}" />
        </div>
      </li>
      <li class="upload card" @click="openFile" v-if="!uploadVisible && !inputDisabled">
        <el-icon>
          <Plus />
        </el-icon>
      </li>
      <li class="upload card" @click="openFile" v-else-if="!fileList.length">
        <el-icon>
          <Picture />
        </el-icon>
      </li>
    </ul>
    <template v-if="!uploadVisible && !inputDisabled">
      <div class="button-file" v-if="props.type === 'files'">
        <el-button type="primary" @click="openFile">
          选择文件
        </el-button>
      </div>
      <div class="upload-tip">
        <p v-if="props.len > 1">
          最大上传数量：{{ props.len }}
        </p>
        <slot name="tip">
          <p>{{ uploadTip }}</p>
        </slot>
      </div>
    </template>
    <ul class="file-list" v-if="props.type === 'files' && fileList.length">
      <li class="file-item" v-for="(item, i) in fileList" :key="i">
        <el-image
          v-if="item.type === 'img'"
          v-show="false"
          :ref="(el: any) => { if (el) item.ref = el }"
          :src="item.src"
          fit="contain"
          hide-on-click-modal
          preview-teleported
          :data-id="item.src"
          :preview-src-list="[item.src]"
        />
        <SvgIcon :font="item.type" />
        <p class="file-name" :title="item.name">
          {{ item.name }}
        </p>
        <div class="right-utils" v-if="item.progress === 100">
          <el-icon v-if="item.type === 'img'" @click="previewImg(item)" :size="18">
            <ZoomIn />
          </el-icon>
          <el-icon v-else @click="download(item)" :size="18">
            <Download />
          </el-icon>
          <el-icon v-if="!inputDisabled" @click="deleteImg(item, i)" :size="18">
            <Close />
          </el-icon>
        </div>
        <div class="up-progress" v-show="item.progress && item.progress < 100">
          <p>{{ item.progress || 0 }}%</p>
          <div class="progress-bar" :style="{width: `${item.progress}%`}" />
        </div>
      </li>
    </ul>
    <input
      :disabled="inputDisabled"
      type="file"
      name="file"
      ref="fileDom"
      :accept="acceptComputed"
      @change="changeFile"
      v-show="false"
    >
  </div>
</template>

<script lang="ts" setup>
import { uploadFile } from '@/api/upload'
import { IImage } from '@/types/element-plus'
import { Plus, ZoomIn, Delete, Download, Close, Picture } from '@element-plus/icons'
import { ElMessage, useDisabled } from 'element-plus'
import { computed, ref } from 'vue'
import SvgIcon from '../Icon/SvgIcon.vue'

interface FileItem {
  file?: File | null
  src: string
  ref?: IImage
  type?: string
  name?: string
  progress?: number
  loading?: boolean
  uploadPromise?: Promise<string>
}
const props = defineProps({
  // 上传格式,files/image
  type: {
    type: String,
    default: 'files'
  },
  // 上传图片数量限制
  len: {
    type: Number,
    default: 1
  },
  // 上传类型img、excel、word、zip、apk
  accept: {
    type: String || Array,
    default: ''
  },
  // 屏蔽删除按钮
  disabled: {
    type: Boolean,
    default: false
  }
})
const fileDom = ref<HTMLInputElement>()
const fileList = ref<FileItem[]>([])
const inputDisabled = useDisabled()

// 获取文件类型
const getFileType = (name: string = '') => {
  return [
    {
      type: 'img',
      reg: /\.(gif|jpg|jpeg|png|GIF|JPG|PNG)$/.test(name),
      tip: 'gif，jpeg，jpg，png',
      msg: '图片类型必须是gif，jpeg，jpg，png中的一种',
      accept: 'image/gif,image/jpeg,image/jpg,image/png,image/svg',
      isAccept: props.accept.includes('img')
    },
    {
      type: 'excel',
      reg: /\.(xls|csv|xlsx|XLS|CSV|XLSX)$/.test(name),
      tip: 'xls，csv，xlsx',
      msg: '表格类型必须是xls，csv，xlsx中的一种',
      accept: '.csv,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      isAccept: props.accept.includes('excel')
    },
    {
      type: 'word',
      reg: /\.(doc|docx|xml|DOC|DOCX|XML)$/.test(name),
      tip: 'doc，docx，xml',
      msg: '文档类型必须是doc，docx，xml中的一种',
      accept: '.doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document',
      isAccept: props.accept.includes('word')
    },
    {
      type: 'zip',
      reg: /\.(zip|rar|ZIP|RAR)$/.test(name),
      tip: 'zip，rar',
      msg: '压缩包必须是zip，rar中的一种',
      accept: '.zip,.rar,application/x-rar-compressed,application/zip,application/x-zip-compressed,application/octet-stream',
      isAccept: props.accept.includes('zip')
    },
    {
      type: 'apk',
      reg: /\.(apk|APK)$/.test(name),
      tip: 'apk',
      msg: '文件必须是apk，APK中的一种',
      accept: 'application/vnd.android.package-archive',
      isAccept: props.accept.includes('apk')
    }
  ]
}

// input accept值
const acceptComputed = computed(() => {
  let accept = ''
  const files = getFileType()
  files.forEach(item => {
    if (item.isAccept) {
      accept += item.accept
    }
  })
  return accept
})

// 默认文字提示
const uploadTip = computed(() => {
  let tip = ''
  const files = getFileType()
  files.forEach(item => {
    if (item.isAccept) {
      tip += `${item.tip}，`
    }
  })
  return `支持上传文件类型：${tip ? tip.slice(0, tip.length - 1) : '所有文件'}`
})

const uploadVisible = computed(() => fileList.value.length >= props.len)

// 打开选择文件
const openFile = () => {
  fileDom.value?.click()
}

// 选择图片
const changeFile = async (e:Event) => {
  const files = (e.target as HTMLInputElement).files
  const file = files && files[0]
  // const src = window.URL.createObjectURL(fileImg as Blob)
  if (fileDom.value) fileDom.value.value = ''
  const name = file?.name || ''

  const testMessage = getFileType(name)
  const matchAccept = testMessage.filter(item => item.isAccept)
  // 单文件格式
  if (matchAccept.length === 1) {
    const current = matchAccept[0]
    if (!current.reg) {
      return ElMessage.error(current.msg)
    }
  }
  // 多文件格式
  if (matchAccept.length > 1) {
    if (matchAccept.every(item => !item.reg)) {
      return ElMessage.error('上传文件格式有误，请重试')
    }
  }
  const f = testMessage.find(item => item.reg)
  const index = fileList.value.push({
    type: f?.type || 'file',
    name: name,
    src: '',
    loading: true
  })
  try {
    const data = await uploadFile({ file, type: 'avatar' }, progress => {
      fileList.value[index - 1].progress = progress
    })
    fileList.value[index - 1].src = data
    // 图片类型加载完成后显示
    if (fileList.value[index - 1].type === 'img') {
      const img = new Image()
      img.src = data
      img.onload = () => {
        fileList.value[index - 1].loading = false
      }
      return
    }
    fileList.value[index - 1].loading = false
  } catch (e) {
    fileList.value.splice(index, 1)
    ElMessage.error('上传失败，请重试')
  }
}

// 放大图片
const previewImg = (item: FileItem) => {
  if (item.type === 'img') {
    const dom = document.querySelector(`img[data-id='${item.src}']`) as HTMLElement
    dom && dom.click()
  }
}
// 删除图片
const deleteImg = (item: FileItem, index: number) => {
  fileList.value.splice(index, 1)
}
// 下载文件
const download = (item: FileItem) => {
  if (props.accept !== 'img') window.open(item.src)
}
// 获取上传结果
const getFiles = () => {
  if (!fileList.value.length) return {}
  fileList.value.forEach(item => {
    if (!item.src) {
      ElMessage.warning('文件正在上传中，请稍后再试')
      throw Error('文件正在上传中，请稍后再试')
    }
  })
  return fileList.value.length === 1 ? fileList.value[0] : fileList.value
}

// 设置已上传的文件，会刷新原值
const setFiles = (list: string[] | null = []) => {
  fileList.value = []
  if (!list || !list.length) return
  // if (list.length > props.len) return ElMessage.error('文件数量超出限制,请检查len参数')
  list.forEach(addFile)
}

// 添加文件
const addFile = (src: string = '') => {
  // if (fileList.value.length >= props.len) return ElMessage.error('文件数量超出限制,请检查len参数')
  if (!src) return
  const files = getFileType(src).find(item => item.reg)
  const item = {
    src,
    name: src,
    type: props.type === 'image' ? 'img' : files?.type || 'file',
    progress: 100,
    loading: false
  }
  fileList.value.push(item)
}
defineExpose({ getFiles, setFiles, addFile })
</script>

<style lang="scss" scoped>
.upload-image {
  position: relative;
  color: var(--el-text-color-regular);
  line-height: initial;
  width: 100%;
  .picture-list {
    display: flex;
    flex-wrap: wrap;
    &.cut-mb10 {
      margin-bottom: -10px;
    }
    :deep(.el-loading-spinner) {
      transform: none;
    }
    .card {
      position: relative;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      background-color: #fbfdff;
      width: 150px;
      height: 150px;
      box-sizing: border-box;
      border-radius: 5px;
      overflow: hidden;
      > .el-icon {
        font-size: 28px;
      }
      > p {
        margin: 0 10px;
        word-break: break-word;
        text-overflow: ellipsis;
        overflow: hidden;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
      }
    }
    .picture {
      // border: 1px solid #c0ccda;
      margin-right: 10px;
      margin-bottom: 10px;
      .el-image {
        width: 100%;
        height: 100%;
      }
      .card-actions {
        display: flex;
        justify-content: center;
        align-items: center;
        position: absolute;
        font-size: 20px;
        width: 100%;
        height: 100%;
        background: #00000080;
        color: var(--el-color-white);
        opacity: 0;
        transition: opacity var(--el-transition-duration);
        &:hover {
          opacity: 1;
        }
        .el-icon {
          cursor: pointer;
          margin-right: 20px;
          &:last-child {
            margin: 0;
          }
        }
      }
    }
    .upload {
      border: 1px dashed #c0ccda;
      cursor: pointer;
      vertical-align: top;
      margin-bottom: 10px;
      &:hover , &:focus{
        border-color: var(--el-color-primary);
        color: var(--el-color-primary);
      }
    }
  }
  .file-list {
    .file-item {
      display: flex;
      align-items: center;
      position: relative;
      padding: 5px;
      cursor: pointer;
      &:hover {
        background-color: var(--el-fill-color-light);
        .file-name {
          color: var(--el-color-primary);
        }
        .right-utils {
          display: inline-flex;
        }
      }
      .svg-icon {
        margin-right: 5px;
      }
      .file-name {
        font-size: 14px;
        width: calc(100% - 100px);
        white-space: nowrap;
        text-overflow: ellipsis;
        overflow: hidden;
      }
      .right-utils {
        display: none;
        position: absolute;
        right: 10px;
        top: 50%;
        transform: translate(0, -50%);
        > .el-icon {
          margin-right: 10px;
          &:last-child {
            margin: 0;
          }
          &:hover {
            color: var(--el-color-primary);
          }
        }
      }
    }
  }
  .upload-tip {
    font-size: 12px;
    color: var(--el-text-color-regular);
    margin: 5px 0;
  }
  .up-progress {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 5px;
    z-index: 2001;
    p {
      position: absolute;
      bottom: 100%;
      right: 0;
      text-align: right;
      font-size: 12px;
    }
    .progress-bar {
      background: var(--el-color-primary);
      width: 0;
      height: 100%;
      border-radius: 5px;
      transition: width var(--el-transition-duration);
      overflow: hidden;
    }
  }
}
</style>
